/**
 * Copyright (c) 2010 BullionXpress.com,
 * Permission is hereby granted, free of charge, to any person obtaining
 * a copy of this software and associated documentation files (the
 * "Software"), to deal in the Software without restriction, including
 * without limitation the rights to use, copy, modify, merge, publish,
 * distribute, sublicense, and/or sell copies of the Software, and to
 * permit persons to whom the Software is furnished to do so, subject to
 * the following conditions:
 *
 * The above copyright notice and this permission notice shall be
 * included in all copies or substantial portions of the Software.
 *
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
 * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
 * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
 * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
 * LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
 * OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
 * WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
 *
 */
package com.bullionxpress.client.view;

import com.bullionxpress.shared.util.Log;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Dimension;
import java.awt.Graphics;
import java.awt.Graphics2D;
import java.awt.Image;
import java.awt.RenderingHints;
import java.awt.event.ComponentAdapter;
import java.awt.event.ComponentEvent;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
import java.awt.event.KeyEvent;
import java.awt.event.MouseEvent;
import java.awt.event.MouseListener;
import java.awt.event.MouseMotionListener;
import java.awt.geom.Ellipse2D;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Enumeration;
import java.util.Hashtable;
import java.util.Vector;
import javax.swing.JPanel;
import javax.swing.JScrollBar;
import java.util.Iterator;
import java.util.Timer;
import java.util.TimerTask;
import com.bullionxpress.client.application.ClientGlobals;
import com.bullionxpress.shared.model.Instrument;
import com.bullionxpress.shared.model.Tick;
import com.bullionxpress.shared.model.Quote;
import com.bullionxpress.shared.util.Utility;
import com.bullionxpress.shared.listeners.QuoteListener;

public class ChartView extends JPanel
        implements
        MouseMotionListener,
        MouseListener,
        QuoteListener,
        FocusListener {

    private Color bgColor = Utility.getColor("#0e1727");
    private Color labelForeground = Color.white;
    private Color bidAskOutsideColor = Color.white;
    private Color bidAskInsideColor = Utility.getColor("#FFD700");
    private Color labelBackground = Color.darkGray;
    private Color currentPriceLabelColorBg = Color.lightGray;
    private Color currentPriceLabelColorFg = Color.black;
    private Color currentPriceLabelColorBorder = Color.darkGray;
    private Color axisForegroundColor = Color.black;
    private Color axisBackgroundColor = Utility.getColor("#bbc2ce"); 
    private Color axisGridBackgroundColor = Color.darkGray;
    private Color tickLineColor = Color.black;
    private Color crossHairColor = Color.white;
    private Color labelBackgroundBorder = Color.black;
    
    public final static int PLOT_CANDLE_STICK = 0;
    public final static int PLOT_OHLC = 1;
    public final static int PLOT_BIDASK = 7;
    public final static int PLOT_LINE_TYPE = 2;
    public final static int PLOT_LINE_TYPE_COLORED = 6;
    public final static int PLOT_HLC = 3;
    public final static int PLOT_DOT = 4;

    final static int CHART_WIDTH = 800;
    final static int CHART_HEIGHT = 600;

    private Thread thread = null;
    private Vector tickList = new Vector(); // visible ticks
    private Vector indicators = new Vector();

    private int fps = 24;
    private Image buf = null;

    private int offset = 0;
    private Tick previousTick = null;

    private MouseEvent mousePosition = null;
    private double maximumPrice = 0;
    private double minimumPrice = 0;
    private int tickWidth = 1;//3;
    private int spacer = 1;//3;

    private Instrument instrument = new Instrument("GOLD");
    private boolean dragging = false;
    private int axisHeight = 25;
    private int axisWidth = 60;
    private int axisBarHeight = 2;
    private int chartType = PLOT_BIDASK;
    private JScrollBar scrollBar = null;
    private int pos = 0;

    private Hashtable indicatorCache = new Hashtable();
    private double per = 5;

    public ChartView() {
        setDoubleBuffered(true);
        buf = createImage(getWidth(), getHeight());
        addMouseMotionListener(this);
        addMouseListener(this);
        addFocusListener(this);

        Timer timer = new Timer();
        final ChartView c = this;
        TimerTask task = new TimerTask() {

            public void run() {
                c.repaint();
            }
        };
        timer.scheduleAtFixedRate(task, 0, fps);

        addComponentListener(new ComponentAdapter() {

            @Override
            public void componentResized(ComponentEvent event) {
                try {
                    if (getSize() != null && getSize().width > 0 && getSize().height > 0) {
                        setSize(getSize());
                        validate();
                    }
                } catch (Exception ex) {
                    ex.printStackTrace();
                }
            }
        });

        int w = getWidth();
        int h = getHeight();
        setSize(w, h);

        validate();
        //start();
    }

    /////////////////////////////////////////////////////////////////////////////
    public void addIndicator(Indicator g) {
        if (g != null) {
            indicators.addElement(g);
        }
    }

    public void removeIndicator(Indicator g) {
        if (g != null) {
            Enumeration e = indicators.elements();
            while (e.hasMoreElements()) {
                Indicator indicator = (Indicator) e.nextElement();
                if (indicator.compareTo(g) == 0) {
                    indicators.remove(indicator);
                    return;
                }
            }
        }
    }

    public void removeIndicator(String uid) {
        if (uid != null) {
            Enumeration e = indicators.elements();
            while (e.hasMoreElements()) {
                Indicator indicator = (Indicator) e.nextElement();
                if (indicator.getUID().equals(uid)) {
                    indicators.remove(indicator);
                    return;
                }
            }
        }
    }

    public Vector getIndicators() {
        return indicators;
    }

    //////////////////////////////////////////////////////////////////////////////
    public int getTickCount() {
        return tickList.size();
    }

    public void setChartType(int type) {
        chartType = type;
    }

    public void setAxisHeight(int height) {
        axisHeight = height;
    }

    public int getAxisHeight() {
        return axisHeight;
    }

    public void setAxisWidth(int width) {
        axisWidth = width;
    }

    public int getAxisWidth() {
        return axisWidth;
    }

    public void focusGained(FocusEvent e) {
        thread.setPriority(Thread.NORM_PRIORITY);
    }

    public void focusLost(FocusEvent e) {
        thread.setPriority(Thread.MIN_PRIORITY);
    }

    public void setLabelForeground(Color c) {
        this.labelForeground = c;
    }

    public Color getLabelForeground() {
        return this.labelForeground;
    }

    public void setLabelBackground(Color c) {
        this.labelBackground = c;
    }

    public Color getLabelBackground() {
        return this.labelBackground;
    }

    public void setVisible(boolean aFlag) {
        super.setVisible(aFlag);
    }

    public void setInstrument(Instrument instrument, Vector newData) {
        try {
            if (newData == null) {
                Log.info("Loader returned no data");
                throw new IllegalArgumentException("Program cannot call with null data");
            }

            Log.info("Set and clear");
            this.instrument = instrument;
            tickList = null;
            tickList = new Vector();

            minimumPrice = 0;
            maximumPrice = 0;
            mousePosition = null;
            dragging = false;

            indicatorCache = null;
            indicatorCache = new Hashtable();

            System.gc();

            Iterator e = newData.iterator();
            Log.info("Loaded:" + newData.size());
            Log.info("Started adding ticks");
            Tick t = null;
            while (e.hasNext()) {
                t = (Tick) e.next();
                add(t);
            }
            Log.info("Done");
            repaint();
            validate();

            Log.info("Setting Scrollbar");
            if (scrollBar != null) {
                try {
                    int max = getScrollBarMaximum();
                    scrollBar.setMaximum(max);
                    scrollBar.setMinimum(0);
                    scrollBar.setValueIsAdjusting(true);
                    scrollBar.setBlockIncrement(1);
                    scrollBar.setUnitIncrement(1);
                    scrollBar.setValue(0);

                } catch (Exception ex) {
                    Log.info(ex);
                }
            }
        } catch (Exception ex) {
            Log.info(ex);
            ex.printStackTrace();
        }
    }

    public int getScrollBarMaximum() {
        int v = 0;
        v = tickList.size() - ((getWidth() - axisWidth) / spacer);
        v += 10;//wtf?
        if (v < 0) {
            v = 0;
        }
        return v;
    }

    public Instrument getInstrument() {
        return instrument;
    }

    public Tick select(int x, int y) {
        Tick retVal = null;
        int elem = 0;
        try {
            elem = (x / spacer) + pos;
            if (tickList.size() > elem) {
                try {
                    retVal = (Tick) tickList.elementAt(elem);
                } catch (Exception ex) {
                }
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }

        return retVal;
    }

    private void drawBackground() {
        Graphics2D g = (Graphics2D) getBackbuffer().getGraphics();
        g.setColor(bgColor);
        g.fillRect(0, 0, getWidth(), getHeight());

    }

    private void drawCrossHair() {
        if (mousePosition != null) {
            Graphics2D g = (Graphics2D) getBackbuffer().getGraphics();
            g.setColor(crossHairColor);
            if (mousePosition.getY() < (getHeight() - axisHeight - 1)) {
                g.drawLine(0, mousePosition.getY(), getWidth() - axisWidth - 1, mousePosition.getY());
            }
            if (mousePosition.getX() < (getWidth() - axisWidth - 1)) {
                g.drawLine(mousePosition.getX(), 0, mousePosition.getX(), getHeight() - axisHeight - 1);
            }
        }
    }

    private void drawMousePositionPriceLabel() {
        if (mousePosition == null) {
            return;
        }

        Graphics2D g = (Graphics2D) getBackbuffer().getGraphics();
        g.setColor(currentPriceLabelColorBg);
        g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);

        g.setColor(labelBackground);
        g.setColor(labelBackgroundBorder);
        g.setColor(labelForeground);

        double price = translate(0, (getHeight() - axisHeight), mousePosition.getY(), maximumPrice, minimumPrice);

        int ypos = (int) translate(maximumPrice, minimumPrice, (price), (getHeight() - axisHeight), 0);

        String pattern = "";
        pattern = "#,##0.00;";

        DecimalFormat df = new DecimalFormat(pattern);
        String output = df.format(price);

        int fsz = 20;
        int labelWidth = axisWidth - 3;

        g.fillRect(getWidth() - axisWidth + 2, (getHeight() - axisHeight) - ypos - (fsz / 2), labelWidth, fsz);
        g.setColor(currentPriceLabelColorBorder);
        g.drawRect(getWidth() - axisWidth + 2, (getHeight() - axisHeight) - ypos - (fsz / 2), labelWidth, fsz);

        g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);

        g.setColor(currentPriceLabelColorFg);
        g.drawString(String.valueOf(output), getWidth() - axisWidth + 7, ((getHeight() - axisHeight) - ypos - (fsz / 2)) + ((fsz / 2) + (fsz / 4)));
    }

    private void drawCurrentPriceLabel() {
        Tick t = null;
        try {
            if (tickList.size() > 0) {
                t = (Tick) tickList.elementAt(tickList.size() - 1);
            }
        } catch (Exception ex) {
            ex.printStackTrace();
        }

        if (t == null) {
            return;
        }

        Graphics2D g = (Graphics2D) getBackbuffer().getGraphics();

        g.setColor(Color.ORANGE);

        String pattern = "";
        pattern = "#,##0.00;";

        DecimalFormat df = new DecimalFormat(pattern);
        String currentPriceClose = df.format(t.close);
        String currentPriceOpen = df.format(t.open);

        int closeypos = (int) translate(maximumPrice, minimumPrice, (t.close), (getHeight() - axisHeight), 0);
        int openypos = (int) translate(maximumPrice, minimumPrice, (t.open), (getHeight() - axisHeight), 0);
        int fsz = 20;
        int labelWidth = axisWidth - 3;
        g.fillRect(getWidth() - axisWidth + 2, (getHeight() - axisHeight) - closeypos - (fsz / 2), labelWidth, fsz);
        g.fillRect(getWidth() - axisWidth + 2, (getHeight() - axisHeight) - openypos - (fsz / 2), labelWidth, fsz);

        g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);

        g.setColor(currentPriceLabelColorFg);
        g.drawString(currentPriceClose, getWidth() - axisWidth + 7, ((getHeight() - axisHeight) - closeypos - (fsz / 2)) + ((fsz / 2) + (fsz / 4)));
        g.drawString(currentPriceOpen, getWidth() - axisWidth + 7, ((getHeight() - axisHeight) - openypos - (fsz / 2)) + ((fsz / 2) + (fsz / 4)));
    }

    private void drawAxis() {
        int labelOffset = 8;

        Graphics2D g = (Graphics2D) getBackbuffer().getGraphics();
        g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
        g.setColor(axisBackgroundColor);

        g.fillRect(getWidth() - axisWidth, 0, axisWidth, getHeight());
        g.setColor(axisForegroundColor);
        g.drawLine(getWidth() - axisWidth, 0, getWidth() - axisWidth, getHeight() - axisHeight);


        int fsz = 20;
        int sz = getHeight() - axisHeight;
        int numGridTicks = 5;
        int unit = sz / numGridTicks;

        boolean firstPass = true;

        for (int p = 0; p < sz; p += unit) {
            if (!firstPass) {

                g.drawLine(getWidth() - axisWidth, p, getWidth() - axisWidth + 2, p);
                double val = translate(0, getHeight() - axisHeight, p, maximumPrice, minimumPrice);

                String pattern = "";
                pattern = "#,##0.00;";

                DecimalFormat df = new DecimalFormat(pattern);
                String output = df.format(val);
                g.drawString(output, getWidth() - axisWidth + labelOffset, p + (fsz / 4));
            } else {
                firstPass = false;
            }
        }

        // DRAW X AXIS
        g.setColor(axisBackgroundColor);
        g.fillRect(0, getHeight() - axisHeight + 1, getWidth() - axisWidth, axisHeight);
        g.setColor(axisForegroundColor);

        g.drawLine(0, getHeight() - axisHeight + 1, getWidth() - axisWidth, getHeight() - axisHeight + 1);

        int count = 0;
        int spacing = 40;
        int mcnt = 0;
        //
        int barsOnScreen = (getWidth() - axisWidth) / spacer;
        int numItems = 6;
        spacing = barsOnScreen / numItems;
        for (int i = 0; i < (getWidth() - axisWidth - tickWidth); i += spacer) {
            g.drawLine(i, getHeight() - axisHeight + 1, i, getHeight() - axisHeight + axisBarHeight);
            if (count == spacing) {
                count = 0;
                g.drawLine(i, getHeight() - axisHeight + 1, i, getHeight() - axisHeight + (axisBarHeight * 3));
                if (mcnt < tickList.size()) {
                    try {
                        Tick tick = (Tick) tickList.elementAt(mcnt - pos);
                        Date d = new Date(tick.getTimestamp());
                        SimpleDateFormat formatter = new SimpleDateFormat("HH:mm:ss");
                        String str = formatter.format(d);
                        //g.drawString(str, i - (g.getFontMetrics().stringWidth(str) / 2), (getHeight() - axisHeight) + 20);
                    } catch (Exception ex) {
                    }
                }
            }
            mcnt++;
            count++;
        }
    }

    private void drawGrid() {
        Graphics2D g = (Graphics2D) getBackbuffer().getGraphics();
        g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);
        g.setColor(this.axisGridBackgroundColor);

        float dash[] = {4.0f};
        g.setStroke(new BasicStroke(1.0f, BasicStroke.CAP_ROUND, BasicStroke.JOIN_BEVEL, 5.0f, dash, 0.0f));

        int sz = getHeight() - axisHeight;
        int numGridTicks = 5;
        int unit = sz / numGridTicks;
        boolean firstPass = true;
        for (int p = 0; p < sz; p += unit) {
            if (!firstPass) {
                g.drawLine(0, p, getWidth() - axisWidth, p);
            } else {
                firstPass = false;
            }
        }

        int spacing = 40;
        int numItems = 6;
        int barsOnScreen = (getWidth() - axisWidth) / numItems;
        spacing = barsOnScreen; /// numItems;
        int aw = (getWidth() - axisWidth - tickWidth);
        for (int i = 0; i < aw; i += spacing) {
            g.drawLine(i, 0, i, getHeight() - axisHeight + axisBarHeight);
        }
    }

    private Image getBackbuffer() {
        if (buf == null) {
            buf = createImage(getWidth(), getHeight());
        }
        return buf;
    }

    private double minPrice(Vector list) {
        double minPrice = 0; // this is a hack.
        Iterator e = list.iterator();
        boolean firstPass = true;
        while (e.hasNext()) {
            Tick t = (Tick) e.next();
            if (t != null) {
                if (firstPass) {
                    minPrice = t.low;
                    firstPass = false;
                }
                minPrice = Math.min(minPrice, t.low);
            }
        }
        return minPrice;
    }

    private double maxPrice(Vector list) {
        double maxPrice = 0;
        Iterator e = list.iterator();
        boolean firstPass = true;
        while (e.hasNext()) {
            Tick t = (Tick) e.next();
            if (t != null) {
                if (firstPass) {
                    maxPrice = t.high;
                    firstPass = false;
                }
                maxPrice = Math.max(maxPrice, t.high);
            }
        }
        return maxPrice;
    }

    public void setSize(Dimension d) {
        try {
            // this will reset the buffer, so that it can be redrawn.
            super.setSize(d);
            buf = null;
            buf = createImage(d.width, d.height);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    public static double translate(double t1, double b1, double p1, double t2, double b2) {
        double answer = b2 + ((p1 - b1) / (t1 - b1) * (t2 - b2));
        return answer;
    }

    private void drawLabel() {
        Graphics2D g = (Graphics2D) getBackbuffer().getGraphics();
        g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);

        int textX = 3;
        int textY = 15;

        g.setColor(labelForeground);
        g.drawString(instrument.getSymbol(), textX, textY);
    }

    private void drawTickBody(Tick t, int index) {
        //try {
        Graphics2D g = (Graphics2D) getBackbuffer().getGraphics();
        Color col = null;
        col = t.getColor();
        g.setColor(col);

        /////////////////////////////////////////////////////
        // body calculations.
        int bpx = 0;
        int bpy = 0;
        int bp2x = 0;
        int bp2y = 0;
        bpx = index * spacer;

        if (t.open > t.close) {
            bpy = (int) translate(maximumPrice, minimumPrice, t.open, getHeight() - axisHeight, 0);
            bp2y = (int) translate(maximumPrice, minimumPrice, maximumPrice - (t.close - t.open), getHeight() - axisHeight, 0);
            bp2y = Math.abs(bp2y);
        } else {
            bpy = (int) translate(maximumPrice, minimumPrice, t.close, getHeight() - axisHeight, 0);
            bp2y = (int) translate(maximumPrice, minimumPrice, maximumPrice - (t.open - t.close), getHeight() - axisHeight, 0);
            bp2y = Math.abs(bp2y);
        }

        /////////////////////////////////////////////////////
        // draw body from the top.

        int bfposx;
        int bfposy;
        int beposy;

        bfposx = bpx;
        bfposy = bpy;
        beposy = bp2y;

        //////////////////////////////////////////////////////
        // draw to context! (upside down!)
        g.fillRect(offset + (bfposx - (tickWidth / 2)), ((getHeight() - axisHeight) - bfposy), tickWidth - 1, (beposy - (getHeight() - axisHeight))); //getHeight() -
        g.setColor(t.getBorderColor());
        g.drawRect(offset + (bfposx - (tickWidth / 2)), ((getHeight() - axisHeight) - bfposy), tickWidth - 1, (beposy - (getHeight() - axisHeight))); //getHeight() -
    }

    // TODO Feature envy?
    private void drawTickLine(Tick t, int index) {
        Graphics2D g = (Graphics2D) getBackbuffer().getGraphics();
        g.setColor(tickLineColor);

        /////////////////////////////////////////////////////
        // line calculations.

        int px = 0;
        int py = 0;
        int p2x = 0;
        int p2y = 0;

        px = index * spacer;
        p2x = index * spacer;
        py = (int) translate(maximumPrice, minimumPrice, t.high, getHeight() - axisHeight, 0);
        p2y = (int) translate(maximumPrice, minimumPrice, t.low, getHeight() - axisHeight, 0);

        /////////////////////////////////////////////////////
        // draw line from the top.
        int fposx;
        int fposy;
        int eposx;
        int eposy;

        fposx = px;
        fposy = py;
        eposx = p2x;
        eposy = p2y;
        g.drawLine(offset + fposx, getHeight() - axisHeight - fposy, offset + eposx, getHeight() - axisHeight - eposy);
    }

    private void plot(Indicator i, int index) {
        Graphics2D g = (Graphics2D) getBackbuffer().getGraphics();
        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g.setColor(i.getColor());

        LineSeg[] lines1 = null;
        LineSeg[] lines2 = null;
        Line line = (Line) indicatorCache.get(i.getUID());
        if (line == null) {
            return;
        }
        if (line.lineSegments.size() > index + 1) {
            lines1 = (LineSeg[]) line.lineSegments.get(index);
            lines2 = (LineSeg[]) line.lineSegments.get(index + 1);
        }

        if (lines1 != null) {
            for (int j = 0; j < lines1.length; j++) {
                if (lines1[j] == null) {
                    return;
                }
                if (lines2[j] == null) {
                    return;
                }
                int y1 = (int) translate(maximumPrice, minimumPrice, lines1[j].y, 0, getHeight() - axisHeight);
                int y2 = (int) translate(maximumPrice, minimumPrice, lines2[j].y, 0, getHeight() - axisHeight);

                int p = tickList.size();

                int offsetX = -(p * spacer);
                int pX1 = (index * spacer) + offsetX;
                int pX2 = ((index * spacer) + spacer) + offsetX;

                if (y1 < getHeight() - axisHeight) {
                    if (y2 < getHeight() - axisHeight) {
                        if (pX1 > 0) {
                            if (pX2 < (getWidth() - axisWidth)) {
                                g.drawLine(pX1, y1, pX2, y2);
                            }
                        }
                    }
                }
            }
        }
    }

    private void plot(Tick t, int index, int type) {
        if (type == PLOT_CANDLE_STICK) {
            drawCandleStick(t, index);
        } else if (type == PLOT_OHLC) {
            drawOHLC(t, index);
        } else if (type == PLOT_BIDASK) {
            drawBidAsk(t, index);
        } else if (type == PLOT_HLC) {
            drawHLC(t, index);
        } else if (type == PLOT_LINE_TYPE) {
            drawLineType(t, index);
        } else if (type == PLOT_LINE_TYPE_COLORED) {
            drawLineTypeColored(t, index);
        } else if (type == PLOT_DOT) {
            drawDOT(t, index);
        }
    }
    
    public void clearData()
    {
        tickList = new Vector();
        previousTick = null;
    }

    public void drawLineType(Tick t, int index) {
        Graphics2D g = (Graphics2D) getBackbuffer().getGraphics();
        g.setColor(Color.green);

        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);

        if (previousTick == null) {
            previousTick = t;
            return;
        }

        int px = 0;
        int py = 0;
        int opy = 0;
        int opx = 0;
        px = index * spacer;
        py = (int) translate(maximumPrice, minimumPrice, t.close, getHeight() - axisHeight, 0);
        opy = (int) translate(maximumPrice, minimumPrice, previousTick.close, getHeight() - axisHeight, 0);
        opx = px - spacer;
        g.drawLine(offset + opx, getHeight() - axisHeight - opy, offset + px, getHeight() - axisHeight - py);
        previousTick = t;
    }

    public void drawLineTypeColored(Tick t, int index) {
        Graphics2D g = (Graphics2D) getBackbuffer().getGraphics();
        g.setColor(t.getColor());

        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g.setRenderingHint(RenderingHints.KEY_TEXT_ANTIALIASING, RenderingHints.VALUE_TEXT_ANTIALIAS_ON);

        if (previousTick == null) {
            previousTick = t;
            return;
        }

        int px = 0;
        int py = 0;
        int opy = 0;
        int opx = 0;
        px = index * spacer;
        py = (int) translate(maximumPrice, minimumPrice, t.close, getHeight() - axisHeight, 0);
        opy = (int) translate(maximumPrice, minimumPrice, previousTick.close, getHeight() - axisHeight, 0);
        opx = px - spacer;
        g.drawLine(offset + opx, getHeight() - axisHeight - opy, offset + px, getHeight() - axisHeight - py);
        previousTick = t;
    }

    public void drawOHLC(Tick t, int index) {
        Graphics2D g = (Graphics2D) getBackbuffer().getGraphics();
        g.setColor(t.getColor());

        int px = 0;
        int py = 0;
        int p2x = 0;
        int p2y = 0;
        /////////////////////////////////////////////////////
        // draw line from the top.
        int fposx;
        int fposy;
        int eposx;
        int eposy;

        px = index * spacer;
        p2x = index * spacer;
        py = (int) translate(maximumPrice, minimumPrice, t.high, getHeight() - axisHeight, 0);
        p2y = (int) translate(maximumPrice, minimumPrice, t.low, getHeight() - axisHeight, 0);

        fposx = px;
        fposy = py;
        eposx = p2x;
        eposy = p2y;
        g.drawLine(offset + fposx, getHeight() - axisHeight - fposy, offset + eposx, getHeight() - axisHeight - (eposy));

        /////////////////////////////////////////////////////
        // body calculations.
        int bpx = 0;
        int bpy = 0;
        int bp2x = 0;
        int bp2y = 0;
        bpx = index * spacer;

        int pxopen = -1;
        int pxclose = -1;
        pxopen = (int) translate(maximumPrice, minimumPrice, t.open, getHeight() - axisHeight, 0);
        pxclose = (int) translate(maximumPrice, minimumPrice, t.close, getHeight() - axisHeight, 0);
        if (t.open > t.close) {
            bpy = (int) translate(maximumPrice, minimumPrice, t.open, getHeight() - axisHeight, 0);
            bp2y = (int) translate(maximumPrice, minimumPrice, maximumPrice - (t.close - t.open), getHeight() - axisHeight, 0);
            bp2y = Math.abs(bp2y);
        } else {
            bpy = (int) translate(maximumPrice, minimumPrice, t.close, getHeight() - axisHeight, 0);
            bp2y = (int) translate(maximumPrice, minimumPrice, maximumPrice - (t.open - t.close), getHeight() - axisHeight, 0);
            bp2y = Math.abs(bp2y);

        }

        /////////////////////////////////////////////////////
        // draw body from the top.

        int bfposx;
        int bfposy;
        int beposy;

        bfposx = bpx;
        bfposy = bpy;
        beposy = bp2y;

        int tailLen = 2;

        // front side. (open)
        g.fillRect(offset + (bfposx - tailLen), ((getHeight() - axisHeight) - pxopen), tailLen, 1); //getHeight() -
        int f = ((getHeight() - axisHeight) - (pxclose));
        // back side. (close)
        g.fillRect(offset + (bfposx + 1), f, tailLen, 1);
    }

    public void drawBidAsk(Tick t, int index) {
        Graphics2D g = (Graphics2D) getBackbuffer().getGraphics();
        g.setColor(bidAskInsideColor);

        int px = 0;
        int py = 0;
        int p2x = 0;
        int p2y = 0;

        if (t.highlighted) {
            g.setColor(Color.red);
        }
        px = index * spacer;
        p2x = index * spacer;
        py = (int) translate(maximumPrice, minimumPrice, t.high, getHeight() - axisHeight, 0);
        p2y = (int) translate(maximumPrice, minimumPrice, t.low, getHeight() - axisHeight, 0);

        /////////////////////////////////////////////////////
        // draw line from the top.
        int fposx;
        int fposy;
        int eposx;
        int eposy;

        fposx = px;
        fposy = py;
        eposx = p2x;
        eposy = p2y;

        for (int i = 0; i < spacer; i++) {
            g.drawLine(offset + fposx + i, getHeight() - axisHeight - fposy, offset + eposx + i, getHeight() - axisHeight - (eposy));
        }

        g.setColor(bidAskOutsideColor);
        g.drawLine(offset + fposx, getHeight() - axisHeight - fposy, offset + fposx + spacer, getHeight() - axisHeight - fposy);
        g.drawLine(offset + fposx, getHeight() - axisHeight - eposy, offset + fposx + spacer, getHeight() - axisHeight - eposy);

    }

    public void drawDOT(Tick t, int index) {
        Graphics2D g = (Graphics2D) getBackbuffer().getGraphics();
        g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
        g.setColor(t.getColor());

        int px = 0;
        int py = 0;
        px = index * spacer;
        py = (int) translate(maximumPrice, minimumPrice, t.close, getHeight() - axisHeight, 0);
        Ellipse2D.Double circle = new Ellipse2D.Double(offset + px, getHeight() - axisHeight - py, tickWidth, tickWidth);
        g.fill(circle);
    }

    public void drawHLC(Tick t, int index) {
        Graphics2D g = (Graphics2D) getBackbuffer().getGraphics();
        g.setColor(t.getColor());

        int px = 0;
        int py = 0;
        int p2x = 0;
        int p2y = 0;

        px = index * spacer;
        p2x = index * spacer;
        py = (int) translate(maximumPrice, minimumPrice, t.high, getHeight() - axisHeight, 0);
        p2y = (int) translate(maximumPrice, minimumPrice, t.low, getHeight() - axisHeight, 0);

        /////////////////////////////////////////////////////
        // draw line from the top.
        int fposx;
        int fposy;
        int eposx;
        int eposy;

        fposx = px;
        fposy = py;
        eposx = p2x;
        eposy = p2y;
        g.drawLine(offset + fposx, getHeight() - axisHeight - fposy, offset + eposx, getHeight() - axisHeight - eposy);

        /////////////////////////////////////////////////////
        // body calculations.
        int bpx = 0;
        int bpy = 0;
        int bp2x = 0;
        int bp2y = 0;
        bpx = index * spacer;

        int pxclose = -1;
        int pxopen = -1;

        pxclose = (int) translate(maximumPrice, minimumPrice, t.close, getHeight() - axisHeight, 0);
        pxopen = (int) translate(maximumPrice, minimumPrice, t.open, getHeight() - axisHeight, 0);

        if (t.open > t.close) {
            bpy = (int) translate(maximumPrice, minimumPrice, t.open, getHeight() - axisHeight, 0);
            bp2y = (int) translate(maximumPrice, minimumPrice, maximumPrice - (t.close - t.open), getHeight() - axisHeight, 0);
            bp2y = Math.abs(bp2y);
        } else {
            bpy = (int) translate(maximumPrice, minimumPrice, t.close, getHeight() - axisHeight, 0);
            bp2y = (int) translate(maximumPrice, minimumPrice, maximumPrice - (t.open - t.close), getHeight() - axisHeight, 0);
            bp2y = Math.abs(bp2y);
        }

        /////////////////////////////////////////////////////
        // draw body from the top.

        int bfposx;
        int bfposy;
        int beposy;

        bfposx = bpx;
        bfposy = bpy;
        beposy = bp2y;

        int tailLen = 2;
        int f = ((getHeight() - axisHeight) - (pxclose));
        g.fillRect((offset + (bfposx) + 1), f, tailLen * 2, 1);
    }

    public void drawCandleStick(Tick t, int index) {
        drawTickLine(t, index);
        drawTickBody(t, index);
    }

    public Vector getVisibleList() {
        // okay - perfect, at least at this screen size.
        // DONT TOUCH!
        Iterator i = tickList.iterator();
        Vector vl = new Vector();
        int sz = tickList.size();
        int m = 0;
        int k = ((getWidth() - axisWidth) / spacer);
        //try { Tick ttt = (Tick)tickList.get(pos); ttt.highlighted = true; } catch(Exception ex){}
        while (i.hasNext()) {
            Tick t = (Tick) i.next();
            if (m >= (pos) && m < (pos + k)) {
                vl.add(t);
            }
            m++;
        }
        return vl;
    }

    public void plot() {
        try {
            Vector vl = getVisibleList();
            maximumPrice = maxPrice(vl);
            minimumPrice = minPrice(vl);

            Iterator i = tickList.iterator();

            int index = 0;
            int sz = tickList.size();
            while (i.hasNext()) {
                try {
                    index++;
                    Tick tick = (Tick) i.next();
                    plot(tick, index - pos, chartType);
                } catch (Exception ex) {
                }
            }

            Iterator indicatorIterator = indicators.iterator();
            while (indicatorIterator.hasNext()) {
                Indicator indicator = (Indicator) indicatorIterator.next();
                populate(indicator);
            }

            index = 0;
            while (index < sz) {
                Iterator plotIterator = indicators.iterator();
                while (plotIterator.hasNext()) {
                    Indicator indicator = (Indicator) plotIterator.next();
                    plot(indicator, index);
                }
                index++;
            }
        } catch (Exception ex) {
            //Log.info(ex);
            //System.out.println(ex);
            //ex.printStackTrace();
        }
    }

    public void populate(Indicator indicator) {

        try {
            Line line = null;
            line = (Line) indicatorCache.get(indicator.getUID());

            if (line == null) {
                line = new Line();
                line.lineColor = indicator.getColor();
                line.uid = indicator.getUID();
                line.lineSegments = new Vector();
            }
            int sz = getTickCount();
            int index = 0;
            while (index < sz) {
                LineSeg[] linesegs = null;
                linesegs = indicator.computeFor(tickList, index);
                if (line != null) {
                    line.lineSegments.add(linesegs);
                }
                index++;
            }
            indicatorCache.put(indicator.getUID(), line);

        } catch (Exception ex) {
            System.out.println("populate indicator:" + ex);
        }

    }

    public void setScrollBar(JScrollBar scrollBar) {
        this.scrollBar = scrollBar;
    }

    public JScrollBar getScrollBar() {
        return scrollBar;
    }

    public void paint(Graphics g) {
        Graphics2D g2 = (Graphics2D) g;
        if (g2 == null || buf == null) {
            return;
        }
        drawBackground();
        drawGrid();
        plot();
        drawAxis();
        drawCurrentPriceLabel();
        if (isDragging()) {
            drawCrossHair();
            drawMousePositionPriceLabel();
        }
        drawLabel();
        //previousSizeOfBackList = tickList.size();
        g2.drawImage(buf, 0, 0, this);
    }

    public void startDragging() {
        dragging = true;
    }

    public void stopDragging() {
        dragging = false;
    }

    public boolean isDragging() {
        return dragging;
    }

    public int getTickWidth() {
        return tickWidth;
    }

    public int getSpacerWidth() {
        return spacer;
    }

    public void setBarWidth(int width) {
        this.tickWidth = width;
    }

    public void setSpacerWidth(int width) {
        spacer = width;
    }

    public void mouseDragged(MouseEvent e) {
        mousePosition = e;
        if (!isDragging()) {
            startDragging();
        }
    }

    public void mouseMoved(MouseEvent e) {
        try {
            mousePosition = e;
            Tick sel = select(e.getX(), e.getY());
        } catch (Exception ex) {
        }
    }

    public void mouseClicked(MouseEvent e) {
    }

    public void mouseEntered(MouseEvent e) {
    }

    public void mouseExited(MouseEvent e) {
        mousePosition = null;
    }

    public void mousePressed(MouseEvent e) {
        Tick t = select(e.getX(), e.getY());

    }

    public void mouseReleased(MouseEvent e) {
        //unhighlight();
        if (isDragging()) {
            stopDragging();
        }
    }

    public void keyPressed(KeyEvent e) {
    }

    public int getScrollPosition() {
        return pos;
    }

    public void scrollTo(int pos) {
        if (pos < 0) {
            pos = 0;
        } else if (pos > scrollBar.getMaximum()) {
            pos = scrollBar.getMaximum();
        } else {
            this.pos = pos;
        }
    }

    public synchronized void add(Tick tick) {
        if (tick == null) {
            Log.info("Throw exception");
            throw new IllegalArgumentException("You cannot add a null tick");
        }
        scrollBar.setValueIsAdjusting(true);
        int sbm = getScrollBarMaximum();
        int abm = scrollBar.getMaximum();
        tickList.addElement(tick);
        ClientGlobals.scrollBarPanel.fireAdjustmentEvent();
        if (pos == sbm) {
            scrollTo(pos + 1);
        }

    }

    public void quote(Quote quote) {
        long snapshot = System.currentTimeMillis();

        if (quote != null) {
            try {
                ClientGlobals.scrollBarPanel.fireAdjustmentEvent();
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }

        if (snapshot - System.currentTimeMillis() > 200) {
            Log.info("This process took a while to complete");
        }

    }
}
